﻿using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentsTagger.DAL;
using DocumentsTagger.DM;
using System.IO;
using System;
using System.Reflection;

using Word = Microsoft.Office.Interop.Word;
using Microsoft.Office.Core;



namespace DocumentsTagger.BL
{
    /// <summary>
    /// This class encapsulate the buisness logic of the system
    /// </summary>
    public class TaggerBL
    {
        private readonly ITaggerDal r_DAL;
        private readonly DirectoriesScanner r_Scanner = new DirectoriesScanner();

        /// <summary>
        /// initialize the buisness logic object
        /// </summary>
        /// <param name="DAL">A concrete DAL object</param>
        public TaggerBL(ITaggerDal DAL)
        {
            this.r_DAL = DAL;
            this.r_Scanner.FoundFile += new FoundFileHandler(Scanner_FoundFile);
        }

        /// <summary>
        /// Function to handling the finding of new document by the scanner
        /// </summary>
        /// <param name="PathToFile">A full path of the file found</param>
        void Scanner_FoundFile(string PathToFile)
        {
            if (!r_DAL.DocumentExists(PathToFile))
            {
                r_DAL.AddDocument(DocumentsFactory.GetDocument(Path.GetDirectoryName(PathToFile),Path.GetFileName(PathToFile)));
            }
        }

        /// <summary>
        /// Get all the documents the the data layer can see
        /// </summary>
        /// <returns>List of documents</returns>
        public List<Document> GetDocuments()
        {
            return r_DAL.GetDocuments();
        }

        /// <summary>
        /// request the dal to truncate the data stores
        /// </summary>
        public void CreateNewDB()
        {
            r_DAL.CreateEmptyDB();
        }

        #region Scanned directories
        /// <summary>
        /// get all scanned directories
        /// </summary>
        /// <returns>a list of folders paths to scan</returns>
        public List<string> GetScannedDirectories()
        {
            return r_DAL.GetScannedDirectories();
        }

        /// <summary>
        /// add a new directory to scan
        /// </summary>
        /// <param name="DirectoryToScan">full path of directory to scan</param>
        /// <exception cref="ArgumentException">if directory is invalid</exception>
        public void AddScannedDirectory(string DirectoryToScan)
        {
            if (!Directory.Exists(DirectoryToScan))
            {
                throw new ArgumentException("Invalid path", "DirectoryToScan");
            }

            r_DAL.AddScannedDirectory(DirectoryToScan);
            r_Scanner.AddFolderToScan(DirectoryToScan);
        }

        /// <summary>
        /// remove scanned directory from scan
        /// </summary>
        /// <param name="DirectoryToScan">full path of directory to scan</param>
        /// <exception cref="InvalidOperationException">if directory is not already scanned</exception>
        public void RemoveScannedDirectory(string DirectoryToRemove)
        {

            if (!r_DAL.GetScannedDirectories().Contains(DirectoryToRemove))
            {
                throw new InvalidOperationException("Directory is not scanned.");
            }

            r_DAL.RemoveScannedDirectory(DirectoryToRemove);
            r_Scanner.RemoveFolderToScan(DirectoryToRemove);
        }
        #endregion  //Scanned directories

        /// <summary>
        /// starts the scanning mechanism
        /// </summary>
        public void StartScanning()
        {
            foreach (string folder in GetScannedDirectories())
            {
                r_Scanner.AddFolderToScan(folder);
            }
            r_Scanner.Start();
        }

        
        public void StopScanning()
        {
            r_Scanner.Stop();
            r_Scanner.Clear();
        }

        public void RemoveDocument(Document document)
        {
            r_DAL.RemoveDocument(document);
        }

        /// <summary>
        /// This function adds a tag to the office document.
        /// </summary>
        /// <param name="fullPath">full path to file to add tag to</param>
        /// <param name="tagValue">the tag name to add</param>
        public void AddTagToWordFile(string fullPath, string tagValue)
        {
            // if does not exists - exit function
            if (!File.Exists(fullPath))
            {
                return;
            }

            Word.Application oWord;
            Word._Document oDoc;
            object oMissing = Missing.Value;
            object oDocBuiltInProps;
            
            //Create an instance of Microsoft Word and make it visible.
            oWord = new Word.Application();
            oWord.Visible = false;

            //Create a new Document and get the BuiltInDocumentProperties collection.
            oDoc = oWord.Documents.Open(fullPath,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing,
                                        ref oMissing);


            oDocBuiltInProps = oDoc.BuiltInDocumentProperties;
            Type typeDocBuiltInProps = oDocBuiltInProps.GetType();

            //Get the keywords
            string strIndex = "Keywords";
            string strValue = string.Empty ;
            string[] splittedTags;
            object oDocAuthorProp = typeDocBuiltInProps.InvokeMember("Item",
                                       BindingFlags.Default |
                                       BindingFlags.GetProperty,
                                       null, oDocBuiltInProps,
                                       new object[] { strIndex });
            Type typeDocAuthorProp = oDocAuthorProp.GetType();

            object tempObj = typeDocAuthorProp.InvokeMember("Value",
                                       BindingFlags.Default |
                                       BindingFlags.GetProperty,
                                       null, oDocAuthorProp,
                                       new object[] { });
            if (tempObj!=null)
            {
                strValue = tempObj.ToString();
            }


            splittedTags = strValue.Split(new string[]{"; "},StringSplitOptions.None);
            if (!splittedTags.Contains(tagValue))
            {
                if (splittedTags.Length != 0)
                {
                    strValue = strValue + "; ";
                }

                strValue = strValue + tagValue;
                
                //Set the Subject property.
                typeDocAuthorProp.InvokeMember("Item",
                                           BindingFlags.Default |
                                           BindingFlags.SetProperty,
                                           null, oDocBuiltInProps,
                                           new object[] { strIndex, strValue });

            }
            oDoc.Close();
            oWord.Quit(true, oMissing, oMissing);

            oWord = null;
        }

        public void AddTag(Document document, string tagValue)
        {
            Tag tag;
            if (!r_DAL.HasTag(tagValue, out tag))
            {
                tag = TagsFactory.GetTag(tagValue);
                r_DAL.AddTag(tag);
            }

            r_DAL.AddAcceptedTagToDocument(document, tag);


            UpdateTagToDocumentProperty(document, tagValue);
        }

        private void UpdateTagToDocumentProperty(Document document, string tagValue)
        {
            if (Path.GetExtension(document.FileName).ToLower() == ".pdf")
            {
                // pdf file
            }
            else
            {
                // word file (doc, docx)
                try
                {
                    this.AddTagToWordFile(document.FullPath, tagValue);
                }
                catch (Exception ex)
                {
                    // todo - write to log
                }
            }
        }

        public void RemoveAcceptedTagFromDocument(Document document, Tag tag)
        {
            r_DAL.RemoveAcceptedTagFromDocument(document, tag);
            document.RemoveAcceptedTags(tag);
            
        }

        public void UpdateDocumentTags(Document doc)
        {
            List<Tag> acceptedTags = r_DAL.GetDocumentAcceptedTags(doc);
            List<Tag> suggestedTags = r_DAL.GetDocumentSuggestedTags(doc);
            List<Tag> declinedTags = r_DAL.GetDocumentDeclinedTags(doc);

            #region add missing tags
            foreach (Tag tag in acceptedTags)
            {
                if (!doc.AcceptedTags.Contains(tag))
                {
                    doc.AddAcceptedTag(tag);
                }
            }

            foreach (Tag tag in suggestedTags)
            {
                if (!doc.SuggestedTags.Contains(tag))
                {
                    doc.AddSuggestedTag(tag);
                }
            }

            foreach (Tag tag in declinedTags)
            {
                if (!doc.DeclinedTags.Contains(tag))
                {
                    doc.AddDeclinedTag(tag);
                }
            }
            #endregion

            #region remove unneeded tags
            int i = 0;
            Tag currTag;
            while (i < doc.AcceptedTags.Count)
            {
                currTag = doc.AcceptedTags[i];
                if (!acceptedTags.Contains(currTag))
                {
                    doc.RemoveAcceptedTags(currTag);
                }
                else
                {
                    i++;
                }
            }
            i = 0;
            while (i < doc.DeclinedTags.Count)
            {
                currTag = doc.DeclinedTags[i];
                if (!declinedTags.Contains(currTag))
                {
                    doc.RemoveDeclinedTags(currTag);
                }
                else
                {
                    i++;
                }
            }
            i = 0;
            while (i < doc.SuggestedTags.Count)
            {
                currTag = doc.SuggestedTags[i];
                if (!suggestedTags.Contains(currTag))
                {
                    doc.RemoveSuggestedTags(currTag);
                }
                else
                {
                    i++;
                }
            }
            #endregion
        }


        public void RemoveDeclineTagFromDocument(Document document, Tag tag)
        {
            r_DAL.RemoveDeclinedTagFromDocument(document, tag);
            document.RemoveDeclinedTags(tag);
        }

        public void DeclineSuggestedTagsForDocument(Document document, Tag tag)
        {
            r_DAL.DeclineSuggestedTagFromDocument(document, tag);
            r_DAL.AddDeclinedTagToDocument(document, tag);
            document.RemoveSuggestedTags(tag);
            document.AddDeclinedTag(tag);
        }

        public void AcceptSuggestedTag(Document document, Tag tag)
        {
            document.AddAcceptedTag(tag);
            document.RemoveSuggestedTags(tag);
            r_DAL.AddAcceptedTagToDocument(document, tag);
            r_DAL.RemoveSuggestedTagFromDocument(document, tag);
            UpdateTagToDocumentProperty(document, tag.Name);
        }
        
        public void RemoveSuggestedTag(Document document, Tag tag)
        {
            document.RemoveSuggestedTags(tag);
            r_DAL.RemoveSuggestedTagFromDocument(document, tag);
        }

        public List<Tag> GetTags()
        {
            return r_DAL.GetTags();
        }

        public List<Document> GetDocumentsByAcceptedTag(Tag tag)
        {
            return r_DAL.GetDocumentsByAcceptedTag(tag);
        }

        public List<Document> GetDocumentsByDeclinedTag(Tag tag)
        {
            return r_DAL.GetDocumentsByDeclinedTag(tag);
        }

        public void AddSuggestedTagToDocument(Document doc, Tag tag)
        {
            r_DAL.AddSuggestedTagToDocument(doc, tag);
            doc.AddSuggestedTag(tag);
        }

        public void AddDeclinedTagToDocument(Document document, Tag tag)
        {
            document.AddDeclinedTag(tag);
            r_DAL.AddDeclinedTagToDocument(document, tag);
        }
    }
}
